package app.server.project.main.service.service.upay.tron;

import app.server.project.core.constants.Def;
import app.server.project.core.exception.SysError;
import app.server.project.core.http.CMSLstResp;
import app.server.project.core.util.AssertUtil;
import app.server.project.core.util.MathUtil;
import app.server.project.main.service.bo.event.upay.tron.ChkTransferEvent;
import app.server.project.main.service.bo.event.upay.tron.TransferEvent;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.user.BlockTransactionDao;
import app.server.project.main.service.db.dao.user.UserWalletDao;
import app.server.project.main.service.db.entity.user.BlockTransactionEntity;
import app.server.project.main.service.db.entity.user.UserWalletEntity;
import app.server.project.main.service.db.mapper.user.BlockTransactionMapper;
import app.server.project.main.service.dto.upay.tron.TXReq;
import app.server.project.main.service.dto.upay.tron.TxDto;
import app.server.project.main.service.service.upay.UPayType;
import app.server.project.main.service.service.upay.tron.constants.TxStatus;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.tron.trident.abi.FunctionEncoder;
import org.tron.trident.abi.TypeReference;
import org.tron.trident.abi.datatypes.Address;
import org.tron.trident.abi.datatypes.Bool;
import org.tron.trident.abi.datatypes.Function;
import org.tron.trident.abi.datatypes.generated.Uint256;
import org.tron.trident.core.ApiWrapper;
import org.tron.trident.core.exceptions.IllegalException;
import org.tron.trident.proto.Chain;
import org.tron.trident.proto.Response;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static app.server.project.main.service.service.upay.UPayType.Tron;
import static app.server.project.main.service.service.upay.tron.constants.Address.Org_Address;
import static app.server.project.main.service.service.upay.tron.constants.Address.Org_Address_Def;
import static app.server.project.main.service.service.upay.tron.constants.TronConstants.Global_Tron_Active_URL;
import static app.server.project.main.service.service.upay.tron.constants.TronConstants.Tron_Active_URL;

@Service
@Slf4j
@RequiredArgsConstructor
public class TronTransactionService {


    private final UserWalletDao userWalletDao;
    private final CfgService cfgService;
    private final GlobalCfgDao globalCfgDao;
    private final TaskScheduler taskScheduler;
    private final BlockTransactionDao transactionDao;
    private final EventService eventService;
    private final BlockTransactionMapper blockTransactionMapper;
    private LocalDateTime next = null;


    public CMSLstResp<TxDto> getAllTx(TXReq req) {
        var list = new ArrayList<TxDto>();
        var start = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
        var uNum = this.blockTransactionMapper.countTron(Tron, start, start.plusDays(1));
        list.add(TxDto.of("未归集U币数量", MathUtil.getNumFormat(uNum, UPayType.U_Rate)));
        var accountNum = this.userWalletDao.getBaseMapper().get10UUserUnActiveWalletList();
        list.add(TxDto.of("待激活账户手续费(trx)", String.valueOf(accountNum.size() * 1.4d)));
        var userWalletEntityList = userWalletDao.getBaseMapper().get10UUserActiveWalletList();
        list.add(TxDto.of("转账手续费(trx)", String.valueOf(userWalletEntityList.size())));
        return CMSLstResp.of(list, list.size());
    }

    /**
     * 批量转账
     */
    public void batchTransferTron() {
        //不可以频繁点击
        if (next == null) {
            next = LocalDateTime.now().plusMinutes(30);
        } else {
            AssertUtil.isTrue(LocalDateTime.now().isAfter(next), SysError.Tron_Active);
        }
        var userWalletEntityList = userWalletDao.getBaseMapper().get10UUserUnActiveWalletList();
        AssertUtil.isTrue(userWalletEntityList.isEmpty(), SysError.Tron_Active);
        userWalletEntityList = userWalletDao.getBaseMapper().get10UUserActiveWalletList();
        for (var wallet : userWalletEntityList) {
            //检查今天是否已经转过账
            //if (LocalDateTimeUtil.isSameDay(wallet.getLastTransferTime(), LocalDateTime.now())) continue;
            List<BlockTransactionEntity> trxList = this.transactionDao.getAllTxByUserId(wallet.getUserId(), Tron);
            long totalAmount = trxList.stream().mapToLong(BlockTransactionEntity::getAmount).sum();
            if (Def.Zero >= totalAmount) {
                continue;
            }
            this.eventService.add(TransferEvent.of(totalAmount, wallet, trxList));
        }
    }


    @EventListener(ApplicationReadyEvent.class)
    @Async(Def.CommonPool)
    public void init() {
        var list = this.transactionDao.getAllUnUpdate();
        if (!list.isEmpty()) {
            for (var txId : list) {
                this.taskScheduler.schedule(() -> {
                    this.eventService.add(ChkTransferEvent.of(txId));
                }, Instant.now().plusSeconds(5 * 60));
            }
        }
    }

    @EventListener(TransferEvent.class)
    public void transferTronEvent(TransferEvent event) throws IllegalException {
        try {
            //先转点手续费
            this.transferTronFee(event.getFromWallet().getAddressHuman());
        } catch (Exception e) {
            log.info("转手续费失败参数:{}", event);
            log.error("转手续费失败异常:{}", event, e);
        }

        //60秒后开始正式转钱
        this.taskScheduler.schedule(() -> {
            //开始转账
            var txId = this.transferTron(event.getFromWallet(), event.getAmount());
            for (var trx : event.getTrxList()) {
                trx.setTxFlag(TxStatus.Fail);
                trx.setTransferTxId(txId);
                this.transactionDao.updateById(trx);
            }
            //更新钱包转账时间,每天一次免费转账
            this.userWalletDao.updateTxTime(event.getFromWallet());
            //开始检查转账是否成功
            this.taskScheduler.schedule(() -> {
                this.eventService.add(ChkTransferEvent.of(txId));
            }, Instant.now().plusSeconds(5 * 60));
        }, Instant.now().plusSeconds(60));

    }

    /**
     * 添加手续费
     */
    public void transferTronFee(String address) throws IllegalException {
        String activeUrl = Tron_Active_URL;
        if (this.globalCfgDao.has(Global_Tron_Active_URL)) {
            activeUrl = this.globalCfgDao.getOneByKey(Global_Tron_Active_URL).getVal();
        }
        ApiWrapper client = this.cfgService.getActiveWalletClient();
        long amount = 15 * 1000000; // Amount in SUN
        Response.TransactionExtention txn = client.transfer(activeUrl, address, amount);
        Chain.Transaction signedTxn = client.signTransaction(txn);
        client.broadcastTransaction(signedTxn);
    }


    @EventListener(ChkTransferEvent.class)
    public void chkTransferTron(ChkTransferEvent event) throws IllegalException {
        if (StrUtil.isBlank(event.getTransactionId())) return;
        var txData = this.cfgService.getActiveWalletClient().getTransactionById(event.getTransactionId());
        var contractResult = txData.getRet(0).getContractRet();
        if (contractResult == Chain.Transaction.Result.contractResult.SUCCESS) {
            //批量更新转账成功
            this.transactionDao.updateTxFlag(event.getTransactionId());
        }
    }

    private String transferTron(UserWalletEntity fromWallet, long amount) {
        try {
            String toAddress = Org_Address_Def;
            if (this.globalCfgDao.has(Org_Address)) {
                toAddress = this.globalCfgDao.getStr(Org_Address);
            }
            String contractAddress = this.cfgService.getUSDTContractAddress(); // USDT contract on Nile testnet
            ApiWrapper client = this.cfgService.getClient(fromWallet.getPrivateKey());
            // Create transfer function
            Function transfer = new Function(
                    "transfer",
                    Arrays.asList(
                            new Address(toAddress),
                            new Uint256(amount)
                            // Amount with decimals (e.g., 100 USDT)
                    ),
                    Arrays.asList(new TypeReference<Bool>() {
                    })
            );

            // Encode function call
            String encodedHex = FunctionEncoder.encode(transfer);

            // Trigger contract
            Response.TransactionExtention transactionExtention = client.triggerContract(
                    fromWallet.getAddressHuman(),      // Sender Address
                    contractAddress, // Contract Address
                    encodedHex,      // Encoded function call
                    0,              // call value
                    0,              // token value
                    null,           // token id
                    150_000_000L    // fee Limit
            );

            // Sign and broadcast
            var signedTxn = client.signTransaction(transactionExtention);
            var txId = client.broadcastTransaction(signedTxn);
            client.close();
            return txId;
        } catch (Exception e) {
            log.error("转usdt异常,{}", fromWallet.getUserId(), e);
        }
        return StrUtil.EMPTY;
    }

}
